All files / local reference_set.ts

95.45% Statements 63/66
100% Branches 8/8
90.48% Functions 19/21
98.25% Lines 56/57
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156                                  2x 2x 2x 2x         2x                                 2x   1603x     1603x     1603x     2x 17x       2x 830x 830x 830x       1079x 1079x             2x 175x     1059x 1059x             706x 706x 706x 706x 706x 433x       2x       2x 608x 608x 608x 490x       2x 231x 231x 231x 231x 231x 26x   231x     2x 1324x     2x       1419x 1419x 1419x       2x   2x   7948x 7948x       2x 9752x             2x 5052x         2x  
/**
 * Copyright 2017 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
import { BatchId, TargetId } from '../core/types';
import { documentKeySet, DocumentKeySet } from '../model/collections';
import { DocumentKey } from '../model/document_key';
import { primitiveComparator } from '../util/misc';
import { SortedSet } from '../util/sorted_set';
 
import { GarbageCollector } from './garbage_collector';
import { GarbageSource } from './garbage_source';
import { PersistenceTransaction } from './persistence';
import { PersistencePromise } from './persistence_promise';
 
/**
 * A collection of references to a document from some kind of numbered entity
 * (either a target ID or batch ID). As references are added to or removed from
 * the set corresponding events are emitted to a registered garbage collector.
 *
 * Each reference is represented by a DocumentReference object. Each of them
 * contains enough information to uniquely identify the reference. They are all
 * stored primarily in a set sorted by key. A document is considered garbage if
 * there's no references in that set (this can be efficiently checked thanks to
 * sorting by key).
 *
 * ReferenceSet also keeps a secondary set that contains references sorted by
 * IDs. This one is used to efficiently implement removal of all references by
 * some target ID.
 */
export class ReferenceSet implements GarbageSource {
  // A set of outstanding references to a document sorted by key.
  private refsByKey = new SortedSet(DocReference.compareByKey);
 
  // A set of outstanding references to a document sorted by target id.
  private refsByTarget = new SortedSet(DocReference.compareByTargetId);
 
  /** Keeps track of keys that have references */
  private garbageCollector: GarbageCollector | null = null;
 
  /** Returns true if the reference set contains no references. */
  isEmpty(): boolean {
    return this.refsByKey.isEmpty();
  }
 
  /** Adds a reference to the given document key for the given ID. */
  addReference(key: DocumentKey, id: TargetId | BatchId): void {
    const ref = new DocReference(key, id);
    this.refsByKey = this.refsByKey.add(ref);
    this.refsByTarget = this.refsByTarget.add(ref);
  }
 
  /** Add references to the given document keys for the given ID. */
  addReferences(keys: DocumentKeySet, id: TargetId | BatchId): void {
    keys.forEach(key => this.addReference(key, id));
  }
 
  /**
   * Removes a reference to the given document key for the given
   * ID.
   */
  removeReference(key: DocumentKey, id: TargetId | BatchId): void {
    this.removeRef(new DocReference(key, id));
  }
 
  removeReferences(keys: DocumentKeySet, id: TargetId | BatchId): void {
    keys.forEach(key => this.removeReference(key, id));
  }
 
  /**
   * Clears all references with a given ID. Calls removeRef() for each key
   * removed.
   */
  removeReferencesForId(id: TargetId | BatchId): void {
    const emptyKey = DocumentKey.EMPTY;
    const startRef = new DocReference(emptyKey, id);
    const endRef = new DocReference(emptyKey, id + 1);
    this.refsByTarget.forEachInRange([startRef, endRef], ref => {
      this.removeRef(ref);
    });
  }
 
  removeAllReferences(): void {
    this.refsByKey.forEach(ref => this.removeRef(ref));
  }
 
  private removeRef(ref: DocReference): void {
    this.refsByKey = this.refsByKey.delete(ref);
    this.refsByTarget = this.refsByTarget.delete(ref);
    if (this.garbageCollector !== null) {
      this.garbageCollector.addPotentialGarbageKey(ref.key);
    }
  }
 
  referencesForId(id: TargetId | BatchId): DocumentKeySet {
    const emptyKey = DocumentKey.EMPTY;
    const startRef = new DocReference(emptyKey, id);
    const endRef = new DocReference(emptyKey, id + 1);
    let keys = documentKeySet();
    this.refsByTarget.forEachInRange([startRef, endRef], ref => {
      keys = keys.add(ref.key);
    });
    return keys;
  }
 
  setGarbageCollector(garbageCollector: GarbageCollector | null): void {
    this.garbageCollector = garbageCollector;
  }
 
  containsKey(
    txn: PersistenceTransaction | null,
    key: DocumentKey
  ): PersistencePromise<boolean> {
    const ref = new DocReference(key, 0);
    const firstRef = this.refsByKey.firstAfterOrEqual(ref);
    return PersistencePromise.resolve(
      firstRef !== null && key.isEqual(firstRef.key)
    );
  }
}
 
export class DocReference {
  constructor(
    public key: DocumentKey,
    public targetOrBatchId: TargetId | BatchId
  ) {}
 
  /** Compare by key then by ID */
  static compareByKey(left: DocReference, right: DocReference): number {
    return (
      DocumentKey.comparator(left.key, right.key) ||
      primitiveComparator(left.targetOrBatchId, right.targetOrBatchId)
    );
  }
 
  /** Compare by ID then by key */
  static compareByTargetId(left: DocReference, right: DocReference): number {
    return (
      primitiveComparator(left.targetOrBatchId, right.targetOrBatchId) ||
      DocumentKey.comparator(left.key, right.key)
    );
  }
}